package cn.sylinx.hbatis.plugin.model;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import cn.sylinx.hbatis.db.mapper.MappingFileManager;
import cn.sylinx.hbatis.db.mapper.ModelBuilder;
import cn.sylinx.hbatis.db.mapper.acm.AcmStrategy;
import cn.sylinx.hbatis.db.mapper.acm.UnderlinedAcmStrategy;
import cn.sylinx.hbatis.db.mapper.anno.AttributeColumnMapping;
import cn.sylinx.hbatis.db.mapper.anno.ColumnDesc;
import cn.sylinx.hbatis.db.mapper.anno.PrimaryKey;
import cn.sylinx.hbatis.db.mapper.anno.Table;
import cn.sylinx.hbatis.kit.StrKit;
import cn.sylinx.hbatis.log.GLog;

public enum ModelCacheManager {

	ME;

	private AcmStrategy DEFAULT_ACM_STRATEGY = new UnderlinedAcmStrategy();

	private Map<String, ModelFabric> cached = new ConcurrentHashMap<String, ModelFabric>();

	private ModelCacheManager() {

	}

	public static ModelCacheManager get() {
		return ME;
	}

	public ModelFabric getModelFabric(String clzStr) {

		Class<?> clz = null;
		try {
			clz = Class.forName(clzStr);
			return getModelFabric(clz);
		} catch (ClassNotFoundException e) {
			GLog.error("getModelFabric error:", e);
			return null;
		}
	}

	public ModelFabric getModelFabric(Class<?> clz) {

		ModelFabric mf = cached.get(clz.getName());
		if (mf == null) {
			try {
				mf = cachedOneModel(clz);
				GLog.debug("添加model[{}]至缓存", clz.getName());
				cached.put(clz.getName(), mf);
			} catch (Exception e) {
				GLog.error("cachedOneModel error, clz:" + clz.getName(), e);
			}
		}
		return mf;
	}

	public void init(Set<String> clzSets) {

		if (clzSets == null || clzSets.isEmpty()) {
			return;
		}

		for (String clzStr : clzSets) {
			try {
				Class<?> clz = Class.forName(clzStr);
				Table t = clz.getDeclaredAnnotation(Table.class);
				if (t != null) {
					// 初始化时，只将模型加载到缓存
					ModelFabric mf = cachedOneModel(clz);
					GLog.debug("添加model[{}]至缓存", clzStr);
					cached.put(clzStr, mf);
				}
			} catch (Exception e) {
				GLog.error("init error", e);
			}
		}
	}

	private ModelFabric cachedOneModel(Class<?> clz) throws Exception {

		final AttributeColumnMapping mapping = clz.getAnnotation(AttributeColumnMapping.class);
		final Table table = clz.getAnnotation(Table.class);
		PrimaryKey pk = clz.getAnnotation(PrimaryKey.class);

		Map<String, Field> fieldMap = ModelBuilder.getObjectAllFieldsMap(clz);
		List<Field> fields = ModelBuilder.getObjectAllFields(clz);
		ModelFabric mf = new ModelFabric();
		mf.setClz(clz);
		mf.setFieldMap(fieldMap);
		mf.setFields(fields);
		mf.setMapping(mapping);
		mf.setPrimaryKey(pk);
		mf.setTable(table);

		Map<String, ColumnDesc> fieldDesc = new HashMap<>();
		for (Field f : fields) {
			ColumnDesc cd = f.getAnnotation(ColumnDesc.class);
			if (cd != null) {
				fieldDesc.put(f.getName(), cd);
			}
		}
		mf.setFieldDesc(fieldDesc);

		Map<String, String> attrMapping = new HashMap<String, String>();

		boolean useAcmStrategy = false;

		// 判断全局映射策略
		AcmStrategy globalAcmStrategy = ModelConst.ME.getAcmStrategy();
		if (globalAcmStrategy == null) {

			String acmStrategy = mapping != null ? mapping.acmStrategy() : null;
			if (acmStrategy != null && !"".equals(acmStrategy)) {
				// 判断是否使用策略

				Class<?> acmStrategyClz = Class.forName(acmStrategy);
				Object inst = acmStrategyClz.newInstance();
				if (inst instanceof AcmStrategy) {
					useAcmStrategy = true;
					AcmStrategy as = (AcmStrategy) inst;
					attrMapping.putAll(as.createAttrMapping(mf));

				} else {

					GLog.error("{}不是映射策略", acmStrategy);
					useAcmStrategy = false;
				}
			}

		} else {
			// 使用了全局映射策略
			useAcmStrategy = true;
			attrMapping.putAll(globalAcmStrategy.createAttrMapping(mf));
		}

		// 没有使用策略
		if (!useAcmStrategy) {

			if (mapping == null) {
				// 如果字段映射策略为空，则默认使用下划线策略
				attrMapping.putAll(DEFAULT_ACM_STRATEGY.createAttrMapping(mf));

			} else if (mapping.useAttribute()) {
				// 使用属性

				Set<String> attrs = fieldMap.keySet();
				for (String attr : attrs) {
					attrMapping.put(attr, attr);
				}

			} else {
				// 非属性

				String[] paires = mapping.value();
				// 使用键值对
				for (String paire : paires) {
					String[] p = paire.split(":");
					attrMapping.put(p[0], p[1]);
				}

				// 使用文件
				if (attrMapping.isEmpty()) {
					attrMapping.putAll(getMappingFromFileJavaToDb(mapping.mappingFile()));
				}

			}
		}

		mf.setAttrMapping(attrMapping);

		return mf;
	}

	private static Map<String, String> getMappingFromFileJavaToDb(String resource) {

		Map<String, String> mapping = new HashMap<String, String>();
		if (StrKit.isBlank(resource)) {
			return mapping;
		}

		Properties p = MappingFileManager.get().getMappingFile(resource);
		Set<Object> keys = p.keySet();
		for (Object key : keys) {
			String value = p.getProperty(key.toString());
			mapping.put(key.toString(), value);
		}
		return mapping;
	}

	public void clear() {

		Set<Entry<String, ModelFabric>> entries = cached.entrySet();
		for (Entry<String, ModelFabric> item : entries) {
			item.getValue().clear();
		}
		cached.clear();
	}
}
